package cn.net.yugu.doraemon.common.mybatis;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import cn.net.yugu.doraemon.api.common.enums.BaseEnum;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.EnumTypeHandler;
import org.apache.ibatis.type.JdbcType;

public class DefaultEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

    private final Class<E> type;
    private Map<Object, E> codeMap;
    private EnumTypeHandler<E> handler;

    public DefaultEnumTypeHandler(Class<E> type) {
        if (Objects.isNull(type)) {
            throw new IllegalArgumentException("type can't be null");
        }
        this.type = type;
        //将枚举值，以及枚举类映射存入result中
        final E[] enumConstants = type.getEnumConstants();
        if (enumConstants == null) {
            throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
        }
        if (BaseEnum.class.isAssignableFrom(type)) {
            codeMap = Arrays.stream(enumConstants).map(e -> (BaseEnum) e)
                    .collect(Collectors.toMap(t -> t.getCode(), new Function<BaseEnum, E>() {
                        @Override
                        public E apply(BaseEnum baseEnum) {
                            return (E) baseEnum;
                        }
                    }));
        } else {
            handler = new EnumTypeHandler(type);
        }

    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        if (BaseEnum.class.isAssignableFrom(type)) {
            ps.setObject(i, ((BaseEnum) parameter).getCode());
        } else {
            handler.setNonNullParameter(ps, i, parameter, jdbcType);
        }
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Object object = rs.getObject(columnName);
        if (BaseEnum.class.isAssignableFrom(type)) {
            return codeMap.get(object);
        } else {
            return handler.getNullableResult(rs, columnName);
        }
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Object object = rs.getObject(columnIndex);
        if (BaseEnum.class.isAssignableFrom(type)) {
            return codeMap.get(object);
        } else {
            return handler.getNullableResult(rs, columnIndex);
        }
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Object object = cs.getObject(columnIndex);
        if (BaseEnum.class.isAssignableFrom(type)) {
            return codeMap.get(object);
        } else {
            return handler.getNullableResult(cs, columnIndex);
        }
    }
}
